在第16天,當選擇了一個咖啡方案 (coffee plan) 時,我會在該方案周圍加上邊框。其他咖啡方案則變成非活躍狀態,並移除邊框。CoffeePlan
組件會發出目前啟用方案的名稱給 PlanPicker
組件,以便通知其他咖啡方案移除邊框。
const emit = defineEmits<{
(e: 'selectedPlan', name: string): void
}>()
function selectPlan() {
emit('selectedPlan', props.name)
}
CoffeePlan
組件定義了一個自訂事件,將名稱發送給 PlanPicker
組件。
selectedPlan
函數使用 selectedPlan
事件將方案名稱發送給父組件。PlanPicker
組件接收活躍的方案,並可通知其他方案它們不再是活躍狀態
<script lang="ts">
interface Props {
name: string;
selectedPlan: (name: string) => void;
}
let { name = 'Default Plan', selectedPlan }: Props = $props();
const handleSelectPlan = () => selectedPlan(name);
</script>
CoffeePlan
組件從 $props()
中取得 selectedPlan
函數。
PlanPicker
組件必須提供給 CoffeePlan
組件一個 prop callback,以供 handleSelectPlan
函數調用。
handleSelection
會以方案名稱呼叫 selectedPlan
函數。
@Component({
...
})
export class CoffeePlanComponent {
name = input('Default Plan');
selectedPlan = output<string>();
selectPlan() {
this.selectedPlan.emit(this.name());
}
}
CoffeePlanComponent
宣告了一個 selectedPlan
輸出,用來將咖啡名稱發送給 PlanPickerComponent
。name
是一個字串類型的信號輸入。
當呼叫 selectPlan
方法時,selectedPlan
會將 name
getter 的結果輸出給 PlanPickerComponent
。
<script setup lang="ts">
const props = defineProps({
selected: {
type: Boolean,
default: false,
},
})
</script>
將 selected
新增到 CoffeePlan
的 props
中。當 selected
為 true
時,方案為活躍狀態並帶有邊框;當 selected 為 false 時,則移除邊框。
<template>
<div class="plan" @click="selectPlan" :class="{ 'active-plan': selected }">
<div class="description">
<span class="title"> {{ name }} </span>
</div>
</div>
</template>
div
元素動態綁定到 active-plan
類別 (class)。當 selected
為 true 時,該 CSS 類別會被啟用。否則,類別會被移除。
<script lang="ts">
interface Props {
name: string;
selectedPlan: (name: string) => void;
selected: boolean;
}
let { name = 'Default Plan', selectedPlan, selected }: Props = $props();
const handleSelectPlan = () => selectedPlan(name);
</script>
同樣地,selected
標誌是從 $props
中提取的。此外,Props
介面中有一個類型為 boolean
的 selected
屬性。
<div onclick={handleSelectPlan} class={['plan', selected && 'active-plan']}>
<div class="description">
<span class="title"> {name} </span>
</div>
</div>
當 selected
為 true 時,active-plan
會成為類別列表 (class list) 的一部分。否則,類別列表 (class list) 中不會包含該類別。
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
@Component({
selector: 'app-coffee-plan',
template: `
<div class="plan" (click)="selectPlan()" [class]="{ 'active-plan': selected() }">
<div class="description">
<span class="title"> {{ name() }} </span>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CoffeePlanComponent {
name = input('Default Plan');
selected = input(false);
selectedPlan = output<string>();
}
selected
是一個初始為 false 的信號輸入。
div
元素的 class
屬性綁定到一個物件。當 selected
的 getter 函式返回 true 時,active-plan
類別 (class) 會被啟用。否則,該類別 (class) 會從元素中移除,邊框也不會顯示。
// PlanPicker
<script setup lang="ts">
const selectedPlan = ref('')
function handleSelectPlan(name: string) {
selectedPlan.value = name
}
function isSelected(plan: string) {
return selectedPlan.value === plan
}
</script>
<template>
<div class="plans">
<CoffeePlan
v-for="plan in plans"
:key="plan"
:name="plan"
:selected="isSelected(plan)"
@selectedPlan="handleSelectPlan"
>
</CoffeePlan>
</div>
</template>
當 CoffeePlan
發出 selectedPlan
事件時,handleSelectedPlan
會更新 selectedPlan
ref。isSelected
函式用來判斷是否有選擇咖啡方案 (coffee plan)。接著,該函式會將結果指派給 CoffeePlan
組件的 selected
輸入。當方案被選中時,CSS 會為其添加邊框,否則咖啡方案不會顯示邊框。
<script lang="ts">
let selectedCoffeePlan = $state('');
const selectedPlan = (name: string) => (selectedCoffeePlan = name);
const isSelected = (plan: string) => selectedCoffeePlan === plan;
</script>
<div class="plans">
{#each plans as plan (plan)}
<CoffeePlan name={plan} {selectedPlan} selected={isSelected(plan)} />
{/each}
</div>
當 CoffeePlan
發出 selectedPlan
事件時,selectedPlan
會更新 selectedCoffeePlan
rune。isSelected
函式用來判斷是否有選擇咖啡方案。接著,該函式會將結果指派給 CoffeePlan
組件的 selected
輸入。當方案被選中時,CSS 會為其添加邊框,否則咖啡方案不會顯示邊框。
@Component({
selector: 'app-plan-picker',
})
export class PlanPickerComponent {
selectedPlan = signal('');
handleSelectPlan(name: string) {
this.selectedPlan.set(name);
}
isPlanSelected(planName: string): boolean {
return this.selectedPlan() === planName;
}
}
<div class="plans">
@for (plan of plans(); track plan) {
<app-coffee-plan
[name]="plan"
(selectedPlan)="handleSelectPlan($event)"
[selected]="isPlanSelected(plan)"
/>
}
</div>
當 PlanPickerComponent
發出 selectedPlan
事件時,handleSelectPlan
方法會更新 selectedPlan
信號。isPlanSelected
方法用來判斷是否有選擇咖啡方案。接著,該方法會將結果指派給 CoffeePlan
組件的 selected
輸入。當方案被選中時,CSS 會為其添加邊框,否則咖啡方案不會顯示邊框。
我們成功地新增了新的 prop 和組件事件 (component event),用於在 CoffeePlan
組件和 PlanPicker
組件之間進行溝通。PlanPicker
從 selected
的值中衍生新的值,並將其傳遞給其他 CoffeePlan
組件,以動態啟用或停用 CSS 類別。